package geecache

import (
	"fmt"
	"geecache/singleflight"
	"log"
	"sync"
)

/*
geecache/
    |--lru/
        |--lru.go  // lru 缓存淘汰策略
    |--byteview.go // 缓存值的抽象与封装
    |--cache.go    // 并发控制
    |--geecache.go // 负责与外部交互，控制缓存存储和获取的主流程
*/

type Getter interface {
	Get(key string) ([]byte,error)
}

type GetterFunc func(key string) ([]byte,error)

func (f GetterFunc) Get(key string) ([]byte,error) {
	return f(key)
}

//一个 Group 可以认为是一个缓存的命名空间，每个 Group 拥有一个唯一的名称 name
type Group struct {
	name string
	//缓存未命中时获取源数据的回调(callback)
	getter Getter
	mainCache cache
	peers PeerPicker
	loader *singleflight.Group
}

var (
	mu sync.RWMutex
	groups = make(map[string]*Group)
)

func NewGroup(name string,cacheBytes int64,getter Getter) *Group {
	if getter == nil {
		panic("nil getter")
	}
	mu.Lock()
	defer mu.Unlock()
	g := &Group{
		name: name,
		getter: getter,
		mainCache: cache{cacheBytes: cacheBytes},
		loader: &singleflight.Group{},
	}
	groups[name] = g
	return g
}

func GetGroup(name string) *Group {
	mu.RLock()
	g := groups[name]
	mu.RUnlock()
	return g
}

func (g *Group) Get(key string) (ByteView,error) {
	if key == "" {
		return ByteView{},fmt.Errorf("key is required")
	}

	//从 mainCache 中查找缓存，如果存在则返回缓存值。
	if v,ok := g.mainCache.get(key) ; ok {
		log.Println("[GeeCache] hit")
		return v,nil
	}
	//缓存不存在，则调用 load 方法
	//load 调用 getLocally（分布式场景下会调用 getFromPeer 从其他节点获取）
	//getLocally 调用用户回调函数 g.getter.Get() 获取源数据
	//并且将源数据添加到缓存 mainCache 中（通过 populateCache 方法）
	return g.load(key)
}

func (g *Group) RegisterPeers(peers PeerPicker)  {
	if g.peers != nil {
		panic("RegisterPeerPicker called more than once")
	}
	g.peers = peers
}

func (g *Group) load(key string) (value ByteView,err error) {
	viewi,err := g.loader.Do(key, func() (interface{}, error) {
		if g.peers != nil {
			if peer,ok := g.peers.PickPeer(key);ok {
				if value,err = g.getFromPeer(peer,key); err == nil {
					return value,nil
				}
				log.Println("[GeeCache]Failed to get from peer",err)
			}
		}
		return g.getLocally(key)
	})

	if err == nil {
		return viewi.(ByteView),nil
	}
	return
}

func (g *Group) getFromPeer(peer PeerGetter,key string) (ByteView,error) {
	bytes,err := peer.Get(g.name,key)
	if err != nil {
		return ByteView{}, err
	}
	return ByteView{b: bytes},nil
}

func (g *Group) getLocally(key string) (value ByteView,err error) {
	bytes,err := g.getter.Get(key)
	if err != nil {
		return ByteView{},err
	}

	value = ByteView{b: cloneBytes(bytes)}
	g.populateCache(key,value)
	return value,nil
}

func (g *Group) populateCache(key string,value ByteView)  {
	g.mainCache.add(key,value)
}